/*

% Description of my shader.
% Second line of description for my shader.

keywords: material classic

date: YYMMDD

*/

//#include "Lights.fx"
//#include "Ocean.fx"

struct Light
{
    float3 pos;
    float3 dir;
    float4 ambient;
    float4 diffuse;
    float4 spec;
    float3 att; // attenuation parameters (a0, a1, a2)
    float  spotPower;
    float  range;
};

struct SurfaceInfo
{
    float3 pos;
    float3 normal;
    float4 diffuse;
    float4 spec;
};

float3 ParallelLight(SurfaceInfo v, Light L, float3 eyePos)
{
    float3 litColor = float3(0.0f, 0.0f, 0.0f);

    // The light vector aims opposite the direction the light rays travel.
    float3 lightVec = -L.dir;

    // Add the ambient term.
    litColor += v.diffuse * L.ambient;

    // Add diffuse and specular term, provided the surface is in
    // the line of sight of the light.

    float diffuseFactor = dot(lightVec, v.normal);
    [branch]
    if( diffuseFactor > 0.0f )
    {
        float specPower  = max(v.spec.a, 1.0f);
        float3 toEye     = normalize(eyePos - v.pos);
        float3 R         = reflect(-lightVec, v.normal);
        float specFactor = pow(max(dot(R, toEye), 0.0f), specPower);

        // diffuse and specular terms
        litColor += diffuseFactor * v.diffuse * L.diffuse;
        litColor += specFactor * v.spec * L.spec;
    }

    return litColor;
}

float3 PointLight(SurfaceInfo v, Light L, float3 eyePos)
{
    float3 litColor = float3(0.0f, 0.0f, 0.0f);

    // The vector from the surface to the light.
    float3 lightVec = L.pos - v.pos;

    // The distance from surface to light.
    float d = length(lightVec);

    if( d > L.range )
        return float3(0.0f, 0.0f, 0.0f);

    // Normalize the light vector.
    lightVec /= d;

    // Add the ambient light term.
    litColor += v.diffuse * L.ambient;

    // Add diffuse and specular term, provided the surface is in
    // the line of sight of the light.

    float diffuseFactor = dot(lightVec, v.normal);
    [branch]
    if( diffuseFactor > 0.0f )
{
        float specPower  = max(v.spec.a, 1.0f);
        float3 toEye     = normalize(eyePos - v.pos);
        float3 R         = reflect(-lightVec, v.normal);
        float specFactor = pow(max(dot(R, toEye), 0.0f), specPower);

        // diffuse and specular terms
        litColor += diffuseFactor * v.diffuse * L.diffuse;
        litColor += specFactor * v.spec * L.spec;
    }

    // attenuate
    return litColor / dot(L.att, float3(1.0f, d, d*d));
}

float3 Spotlight(SurfaceInfo v, Light L, float3 eyePos)
{
    float3 litColor = PointLight(v, L, eyePos);

    // The vector from the surface to the light.
    float3 lightVec = normalize(L.pos - v.pos);

    float s = pow(max(dot(-lightVec, L.dir), 0.0f), L.spotPower);
    // Scale color by spotlight factor.
    return litColor*s;
}

struct OCVS_OUT
{
    float3 pos    : POSITION;
    float3 normalW : NORMAL;
	float2 texC    : TEXCOORD;
    float4 diffuse : DIFFUSE;
    float4 spec    : SPECULAR;
};

struct Wave
{
	float amplitude;
	float velocity;
	float3 direction;
	float3 position;
	float frequency;
	float phase;
	float pi;
	float waveLenght;
	int stepness;

	float last_argument;
	float arg;
	float sinarg;

	float MakeWave(float x, float z, float time)
	{
			position.x = x;
			position.z = z;

			arg = (direction.x * position.x + direction.y * position.y + direction.z * position.z) + time * phase;
			sinarg = sin(arg);

			last_argument = pow(sinarg, stepness);
			return amplitude * last_argument;
	}

	float GetDerivativeX(const float x, const float z, const float time)
	{
		return amplitude * last_argument * stepness * cos(arg) * direction.x / sinarg;
	}

	float GetDerivativeZ(const float x, const float z, const float time)
	{
		return amplitude * last_argument * stepness * cos(arg) * direction.z / sinarg;
	}
};


struct Fog
{
	float4 fogColor;
	float fogStart;
	float fogRange;
};




//----------------------------------------------------------------------------------------------
SamplerState gTriLinearSample
{
	Filter = MIN_MAG_MIP_LINEAR;
};

BlendState gTransparentBlend
{
	BlendEnable[0] = TRUE;
    SrcBlend = SRC_COLOR;
    DestBlend = INV_SRC_ALPHA;
    BlendOp = ADD;
    SrcBlendAlpha = ZERO;
    DestBlendAlpha = ZERO;
    BlendOpAlpha = ADD;
    RenderTargetWriteMask[0] = 0x0F;

};
//-----------------------------------------------------------------------------------------------
// МАТРИЦЫ ПРЕОБРАЗОВАНИЙ
cbuffer cbPerObject
{
    float4x4 gWorld;
    float4x4 gWVP;
	float4x4 gTexMtx;
};

// ПАРАМЕТРЫ ТУМАНА
Fog gFog;


// СЛУЖЕБНЫЕ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
cbuffer cbPerFrame
{
    Light gLight;
    int gLightType;
    float3 gEyePosW;
	float gameTime;
	Light gSunLight;
	int gSunLightFlag;
	int gSpotLightSet;
	Light gSpotLight;
};

struct VS_IN
{
    float3 posL    : POSITION;
    float3 normalL : NORMAL;
	float2 texC    : TEXCOORD;
    float4 diffuse : DIFFUSE;
    float4 spec    : SPECULAR;
};

struct VS_OUT
{
    float3 posL    : POSITION;
    float3 normalL : NORMAL;
    float2 texC    : TEXCOORD;
    float4 diffuse : DIFFUSE;
    float4 spec    : SPECULAR;
};

struct GS_OUT
{
    float4 posH    : SV_POSITION;
    float3 posW    : POSITION;
    float3 normalW : NORMAL;
    float2 texC    : TEXCOORD;
    float4 diffuse : DIFFUSE;
    float4 spec    : SPECULAR;
    float  fogLerp : FOG;
};



/*struct VS_OUT
{
    float4 posH    : SV_POSITION;
    float3 posW    : POSITION;
    float3 normalW : NORMAL;
	float2 texC    : TEXCOORD;
    float4 diffuse : DIFFUSE;
    float4 spec    : SPECULAR;
	float  fogLerp : FOG;
};*/


// ocean
Wave wave1;
Wave wave2;
Wave wave3;
Wave wave4;


//---------------------------------------------------------------------------------------------
Texture2D g_DiffuseMap;
Texture2D g_AlphaMap;


VS_OUT OceanVS(VS_IN vIn)
{
	VS_OUT vOut;

	Wave lwave1 = wave1;
	Wave lwave2 = wave2;
	Wave lwave3 = wave3;
	Wave lwave4 = wave4;
	
	// ocean
	vIn.posL.y = lwave1.MakeWave(vIn.posL.x, vIn.posL.z, gameTime) + lwave2.MakeWave(vIn.posL.x, vIn.posL.z, gameTime) + lwave3.MakeWave(vIn.posL.x, vIn.posL.z, gameTime) + lwave4.MakeWave(vIn.posL.x, vIn.posL.z, gameTime);


	// трансформация в мировое пространство
	//vOut.posW = mul(float4(vIn.posL, 1.0f), gWorld);
	//vOut.normalW = mul(float4(vIn.normalL, 0.0f), gWorld);
	
	// трансформация в гомогенизированное пространство
	//vOut.posH = mul(float4(vIn.posL, 1.0f), gWVP);

	
	// выходняе атрибуты
	vOut.diffuse = vIn.diffuse;
	vOut.spec = vIn.spec;
	vOut.texC = vIn.texC;
	//vOut.fogLerp = 0.0f;
	
	return vOut;
}

VS_OUT mainVS(VS_IN vIn)
{
	VS_OUT vOut;
	
	// трансформация в мировое пространство
	//vOut.posW = mul(float4(vIn.posL, 1.0f), gWorld);
	//vOut.normalW = mul(float4(vIn.normalL, 0.0f), gWorld);
	
	// трансформация в гомогенизированное пространство
	//vOut.posH = mul(float4(vIn.posL, 1.0f), gWVP);

	//vOut.texC = mul(float4(vIn.texC, 0.0, 1.0), gTexMtx);

	// выходняе атрибуты
	vOut.normalL = vIn.normalL;
	vOut.posL = vIn.posL;
	vOut.diffuse = vIn.diffuse;
	vOut.spec = vIn.spec;
	vOut.texC = vIn.texC;
	
	return vOut;
}

[maxvertexcount(7)]
void tesselateGS(triangle VS_OUT gIn[3],
    inout TriangleStream<GS_OUT> triStream)
{
    GS_OUT gOut;

    // не изменяемые выходные данные
    gOut.diffuse = gIn[0].diffuse;
    gOut.spec    = gIn[0].spec;
    
    float dis;
    
    // 1я вершина
    gOut.posW    = mul(float4(gIn[0].posL, 1.0f), gWorld);
	dis = distance(gEyePosW, gOut.posW);
	if(dis > 30.0f)
	{
		gOut.posH    = mul(float4(gIn[0].posL, 1.0f), gWVP);
		gOut.normalW = mul(float4(gIn[0].normalL, 0.0f), gWorld);
		gOut.texC = gIn[0].texC;
		dis = distance(gOut.posW, gEyePosW);
		gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange); 
		triStream.Append(gOut);

		// 2я вершина
		gOut.posW    = mul(float4(gIn[1].posL, 1.0f), gWorld);
		gOut.posH    = mul(float4(gIn[1].posL, 1.0f), gWVP);
		gOut.normalW = mul(float4(gIn[1].normalL, 0.0f), gWorld);
		gOut.texC = gIn[1].texC;
		dis = distance(gOut.posW, gEyePosW);
		gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
		triStream.Append(gOut);
	    
		// 3я вершина
		gOut.posW    = mul(float4(gIn[2].posL, 1.0f), gWorld);
		gOut.posH    = mul(float4(gIn[2].posL, 1.0f), gWVP);
		gOut.normalW = mul(float4(gIn[2].normalL, 0.0f), gWorld);
		gOut.texC = gIn[2].texC;
		dis = distance(gOut.posW, gEyePosW);
		gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
		triStream.Append(gOut);

		return;
	}

	gOut.posH    = mul(float4(gIn[0].posL, 1.0f), gWVP);
	gOut.normalW = mul(float4(gIn[0].normalL, 0.0f), gWorld);
	gOut.texC = gIn[0].texC;
	dis = distance(gOut.posW, gEyePosW);
	gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);
    
    float3 v1 = (gIn[0].posL + gIn[1].posL + gIn[2].posL) / 3.0f;
    float3 n1 = normalize(gIn[0].normalL + gIn[1].normalL + gIn[2].normalL);
	v1 = v1 + 0.05f * n1;
    
    // 2я вершина
    gOut.posW    = mul(float4(v1, 1.0f), gWorld);
	gOut.posH    = mul(float4(v1, 1.0f), gWVP);
	gOut.normalW = mul(float4(n1, 0.0f), gWorld);
	gOut.texC = (gIn[0].texC + gIn[1].texC + gIn[2].texC) / 3.0f;
	dis = distance(gOut.posW, gEyePosW);
	gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);
    
    // 3я вершина
    gOut.posW    = mul(float4(gIn[2].posL, 1.0f), gWorld);
	gOut.posH    = mul(float4(gIn[2].posL, 1.0f), gWVP);
	gOut.normalW = mul(float4(gIn[2].normalL, 0.0f), gWorld);
	gOut.texC = gIn[2].texC;
	dis = distance(gOut.posW, gEyePosW);
	gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);
    
    // 4я вершина
    gOut.posW    = mul(float4(gIn[1].posL, 1.0f), gWorld);
	gOut.posH    = mul(float4(gIn[1].posL, 1.0f), gWVP);
	gOut.normalW = mul(float4(gIn[1].normalL, 0.0f), gWorld);
	gOut.texC = gIn[1].texC;
	dis = distance(gOut.posW, gEyePosW);
	gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);
    
    triStream.RestartStrip();
    
    // 1я вершина
    gOut.posW    = mul(float4(gIn[0].posL, 1.0f), gWorld);
	gOut.posH    = mul(float4(gIn[0].posL, 1.0f), gWVP);
	gOut.normalW = mul(float4(gIn[0].normalL, 0.0f), gWorld);
	gOut.texC = gIn[0].texC;
	dis = distance(gOut.posW, gEyePosW);
	gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);
    
    gOut.posW    = mul(float4(gIn[1].posL, 1.0f), gWorld);
	gOut.posH    = mul(float4(gIn[1].posL, 1.0f), gWVP);
	gOut.normalW = mul(float4(gIn[1].normalL, 0.0f), gWorld);
	gOut.texC = gIn[1].texC;
	dis = distance(gOut.posW, gEyePosW);
	gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);
    
    gOut.posW    = mul(float4(v1, 1.0f), gWorld);
	gOut.posH    = mul(float4(v1, 1.0f), gWVP);
	gOut.normalW = mul(float4(n1, 0.0f), gWorld);
	gOut.texC = (gIn[0].texC + gIn[1].texC + gIn[2].texC) / 3.0f;
	dis = distance(gOut.posW, gEyePosW);
	gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);
    
    return;
    
    /*
    
    gOut.posW    = mul(float4(gIn[0].posL, 1.0f), gWorld);    
    if(distance(gOut.posW, gEyePosW) > 20.0f)
    {
		gOut.posW    = mul(float4(gIn[0].posL, 1.0f), gWorld);
		gOut.posH    = mul(float4(gIn[0].posL, 1.0f), gWVP);
		gOut.normalW = mul(float4(gIn[0].normalL, 0.0f), gWorld);
		gOut.texC = gIn[0].texC;
		dis = distance(gOut.posW, gEyePosW);
		gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
        triStream.Append(gOut);
        
        gOut.posW    = mul(float4(gIn[1].posL, 1.0f), gWorld);
		gOut.posH    = mul(float4(gIn[1].posL, 1.0f), gWVP);
		gOut.normalW = mul(float4(gIn[1].normalL, 0.0f), gWorld);
		gOut.texC = gIn[1].texC;
		dis = distance(gOut.posW, gEyePosW);
		gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
        triStream.Append(gOut);
        
        gOut.posW    = mul(float4(gIn[2].posL, 1.0f), gWorld);
		gOut.posH    = mul(float4(gIn[2].posL, 1.0f), gWVP);
		gOut.normalW = mul(float4(gIn[2].normalL, 0.0f), gWorld);
		gOut.texC = gIn[2].texC;
		dis = distance(gOut.posW, gEyePosW);
		gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
        triStream.Append(gOut);
        
        return;
    
    }
*/
/*
    // Use face normal for vertex normals.
    float3 e0 = gIn[1].posL - gIn[0].posL;
    float3 e1 = gIn[2].posL - gIn[0].posL;
    float3 nz  = normalize( cross(e0, e1) );

    // расчет нормалей
    float3 n[6];
    n[0] = gIn[0].normalL;
    n[1] = normalize(gIn[0].normalL + gIn[1].normalL);
    n[2] = normalize(gIn[2].normalL + gIn[0].normalL);
    n[3] = normalize(gIn[1].normalL + gIn[2].normalL);
    n[4] = gIn[2].normalL;
    n[5] = gIn[1].normalL;
    
    // расчет текстурных координат
    float2 t[6];
    t[0] = gIn[0].texC;
    t[1] = 0.5f * (gIn[0].texC + gIn[1].texC);
    t[2] = 0.5f * (gIn[2].texC + gIn[0].texC);
    t[3] = 0.5f * (gIn[1].texC + gIn[2].texC);
    t[4] = gIn[2].texC;
    t[5] = gIn[1].texC;

    // Compute edge midpoints.
    float3 m0 = 0.5f*(gIn[0].posL+gIn[1].posL) + 0.1f * n[1];
    float3 m1 = 0.5f*(gIn[1].posL+gIn[2].posL) + 0.1f * n[2];
    float3 m2 = 0.5f*(gIn[2].posL+gIn[0].posL) + 0.1f * n[3];

    //       1
    //       *
    //      / \
    //     /   \
    //  m0*-----*m1
    //   / \   / \
    //  /   \ /   \
    // *-----*-----*
    // 0    m2     2

    float3 v[6];
    v[0] = gIn[0].posL;
    v[1] = m0;
    v[2] = m2;
    v[3] = m1;
    v[4] = gIn[2].posL;
    v[5] = gIn[1].posL;

    // We can draw the subdivision in two strips:
    //     Strip 1: bottom three triangles
    //     Strip 2: top triangle

    for(int i = 0; i < 5; ++i)
    {
        gOut.posW    = mul(float4(v[i], 1.0f), gWorld);
        gOut.posH    = mul(float4(v[i], 1.0f), gWVP);
        gOut.normalW = mul(float4(n[i], 0.0f), gWorld);
        // расчет тумана
		float dis = distance(gOut.posW, gEyePosW);
		gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
		gOut.texC = t[i];
        triStream.Append(gOut);
    }
    triStream.RestartStrip();

    gOut.posW    = mul(float4(v[1], 1.0f), gWorld);
    gOut.posH    = mul(float4(v[1], 1.0f), gWVP);
    gOut.normalW = mul(float4(n[1], 0.0f), gWorld);
    gOut.texC = t[1];
    // расчет тумана
	//float dis = distance(gOut.posW, gEyePosW);
	//gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);

    gOut.posW    = mul(float4(v[5], 1.0f), gWorld);
    gOut.posH    = mul(float4(v[5], 1.0f), gWVP);
    gOut.normalW = mul(float4(n[5], 0.0f), gWorld);
    // расчет тумана
    gOut.texC = t[5];
	dis = distance(gOut.posW, gEyePosW);
	gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);

    gOut.posW    = mul(float4(v[3], 1.0f), gWorld);
    gOut.posH    = mul(float4(v[3], 1.0f), gWVP);
    gOut.normalW = mul(float4(n[3], 0.0f), gWorld);
    gOut.texC = t[3];
    // расчет тумана
	dis = distance(gOut.posW, gEyePosW);
	gOut.fogLerp = saturate((dis - gFog.fogStart) / gFog.fogRange);
    triStream.Append(gOut);*/
}



float4 mainPS(GS_OUT pIn) : SV_Target
{
	

	pIn.normalW = normalize(pIn.normalW);


	// apply diffuse color from texture
	float4 diffuse = g_DiffuseMap.Sample(gTriLinearSample, pIn.texC);

	//clip(diffuse.a - 0.25f);
	
	SurfaceInfo v = {pIn.posW, pIn.normalW, diffuse, pIn.spec};
	
	float3 litColor = 0;


	// apply sunlight
	if(gSunLightFlag == 1)
	{
		litColor += ParallelLight(v, gSunLight, gEyePosW);
	}
	
	if(gLightType == 0)	// parallel light
	{
		litColor += ParallelLight(v, gLight, gEyePosW);
	}
	
	else if(gLightType == 1) // point light
	{
		litColor += PointLight(v, gLight, gEyePosW);
	}
	
	else // spot light
	{
		litColor += Spotlight(v, gLight, gEyePosW);
	}

	// apply flashlight
	if(gSpotLightSet == 1)
	{
		litColor += Spotlight(v, gSpotLight, gEyePosW);
	}

	// calculate fog
	float3 foggedColor = lerp(litColor, gFog.fogColor, pIn.fogLerp);
	//foggedColor = pow(foggedColor, 1/2.2);
	//diffuse.a = pow(diffuse.a, 1/2.2);
	
	/* linear tonemapping
	float3 ld = 0.002;
	float linReference = 0.18;
	float logReference = 444;
	float logGamma = 0.45;

	foggedColor.rgb = (log10(0.4*foggedColor.rgb/linReference)/ld*logGamma + logReference)/1023.f;
	foggedColor.rgb  = clamp(foggedColor.rgb , 0, 1);

	float FilmLutWidth = 256;
	float Padding = .5/FilmLutWidth;

//FilmLutSampler
	foggedColor.r = g_DiffuseMap.Sample(gTriLinearSample, float2( lerp(Padding,1-Padding,foggedColor.r), .5)).r;
	foggedColor.g = g_DiffuseMap.Sample(gTriLinearSample, float2( lerp(Padding,1-Padding,foggedColor.g), .5)).r;
	foggedColor.b = g_DiffuseMap.Sample(gTriLinearSample, float2( lerp(Padding,1-Padding,foggedColor.b), .5)).r;
*/

	
	return float4(foggedColor, diffuse.a);
	
	
}

technique10 LightTech {
	pass p0 {
		SetVertexShader(CompileShader(vs_4_0, mainVS() ));
		SetGeometryShader(CompileShader(gs_4_0, tesselateGS() ));
		SetPixelShader(CompileShader(ps_4_0, mainPS() ));
		// Use "blend" for this pass.
		SetBlendState(gTransparentBlend, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xffffffff);
	}
}

/*technique10 OceanTech {
	pass p0 {
		SetVertexShader(CompileShader(vs_4_0, OceanVS() ));
		SetGeometryShader(CompileShader(gs_4_0, tesselateGS() ));
		SetPixelShader(CompileShader(ps_4_0, mainPS() ));
	}
}*/